/*
 * MIT License
 *
 * Copyright (c) 2024 恩爸编程
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.enba.boot.redis;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;

public class RedisOperator {

  private static final Logger logger = LoggerFactory.getLogger(RedisOperator.class);

  private final RedisTemplate<String, Object> redisTemplate;

  public RedisOperator(RedisTemplate<String, Object> redisTemplate) {
    this.redisTemplate = redisTemplate;
  }

  // 字符串类型操作

  /**
   * 设置指定key的字符串值
   *
   * @param key 键
   * @param value 值
   */
  public void set(String key, Object value) {
    redisTemplate.opsForValue().set(key, value);
  }

  /**
   * 设置指定key的字符串值，并设置过期时间
   *
   * @param key 键
   * @param value 值
   * @param timeout 过期时间（秒）
   */
  public void set(String key, Object value, long timeout) {
    redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
  }

  /**
   * 获取指定key的字符串值
   *
   * @param key 键
   * @return 值，如果不存在则返回null
   */
  public Object get(String key) {
    return redisTemplate.opsForValue().get(key);
  }

  /**
   * 获取指定key的字符串值，并转换为指定类型
   *
   * @param key 键
   * @param clazz 目标类型
   * @param <T> 目标对象
   * @return 目标对象，如果不存在则返回null
   */
  public <T> T get(String key, Class<T> clazz) {
    Object value = redisTemplate.opsForValue().get(key);
    if (value == null) return null;
    return JSONObject.parseObject(JSON.toJSONString(value), clazz);
  }

  /**
   * 获取指定key的字符串值，并转换为指定类型
   *
   * @param key 键
   * @param type 目标类型
   * @param <T> 目标对象
   * @return 目标对象，如果不存在则返回null
   */
  public <T> T get(String key, TypeReference<T> type) {
    Object value = redisTemplate.opsForValue().get(key);
    if (value == null) return null;
    return JSONObject.parseObject(JSON.toJSONString(value), type);
  }

  /**
   * 删除指定的key
   *
   * @param key 键
   */
  public void delete(String key) {
    redisTemplate.delete(key);
  }

  // 列表类型操作

  /**
   * 向列表头部添加元素
   *
   * @param key 列表的键
   * @param value 要添加的元素
   * @return 列表长度
   */
  public Long lPush(String key, Object value) {
    return redisTemplate.opsForList().leftPush(key, value);
  }

  /**
   * 从列表头部获取元素
   *
   * @param key 列表的键
   */
  public Object lPop(String key) {
    return redisTemplate.opsForList().leftPop(key);
  }

  /**
   * 从列表头部获取元素 如果你使用的 Redis 服务器版本低于 6.2.0，调用 leftPop(key, count) 会报错
   *
   * @param key 列表的键
   * @param count 要弹出的元素个数
   * @return 从列表头部弹出的元素
   */
  public Object lPop(String key, long count) {
    return redisTemplate.opsForList().leftPop(key, count);
  }

  /**
   * 向列表头部批量添加元素
   *
   * @param key 列表的键
   * @param values 要添加的元素集合
   * @return 列表长度
   */
  public Long lPushAll(String key, Object... values) {
    return redisTemplate.opsForList().leftPushAll(key, values);
  }

  /**
   * 向列表尾部添加元素
   *
   * @param key 列表的键
   * @param value 要添加的元素
   * @return 列表长度
   */
  public Long rPush(String key, Object value) {
    return redisTemplate.opsForList().rightPush(key, value);
  }

  /**
   * 从列表尾部获取元素
   *
   * @param key 列表的键
   * @return 从列表头部弹出的元素
   */
  public Object rPop(String key) {
    return redisTemplate.opsForList().rightPop(key);
  }

  /**
   * 从列表尾部获取元素
   *
   * @param key 列表的键
   * @param count 要弹出的元素个数
   * @return 从列表头部弹出的元素
   */
  public Object rPop(String key, long count) {
    return redisTemplate.opsForList().rightPop(key, count);
  }

  /**
   * 获取列表指定范围内的元素
   *
   * @param key 列表的键
   * @param start 起始索引（包含），0表示第一个元素
   * @param end 结束索引（包含），-1表示最后一个元素
   * @return 元素列表
   */
  public List<Object> lRange(String key, long start, long end) {
    return redisTemplate.opsForList().range(key, start, end);
  }

  /**
   * 删除列表中指定个数的指定元素
   *
   * @param key 列表的键
   * @param count 要删除的个数，正数表示从头部开始匹配删除，负数表示从尾部开始匹配删除，0表示删除所有匹配元素
   * @param value 要删除的元素值
   * @return 删除的元素个数
   */
  public Long lRem(String key, long count, Object value) {
    return redisTemplate.opsForList().remove(key, count, value);
  }

  // 集合类型操作

  /**
   * 向集合中添加元素
   *
   * @param key 集合的键
   * @param values 要添加的元素集合
   * @return 添加成功的元素个数
   */
  public Long sAdd(String key, Object... values) {
    return redisTemplate.opsForSet().add(key, values);
  }

  /**
   * 获取集合中的所有元素
   *
   * @param key 集合的键
   * @return 元素集合
   */
  public Set<Object> sMembers(String key) {
    return redisTemplate.opsForSet().members(key);
  }

  /**
   * 判断元素是否在集合中
   *
   * @param key 集合的键
   * @param value 要判断的元素
   * @return true表示在集合中，false表示不在
   */
  public Boolean sIsMember(String key, Object value) {
    return redisTemplate.opsForSet().isMember(key, value);
  }

  /**
   * 从集合中删除元素
   *
   * @param key 集合的键
   * @param values 要删除的元素集合
   * @return 删除成功的元素个数
   */
  public Long sRem(String key, Object... values) {
    return redisTemplate.opsForSet().remove(key, values);
  }

  // 有序集合类型操作

  /**
   * 向有序集合中添加元素及对应的分数
   *
   * @param key 有序集合的键
   * @param score 元素的分数
   * @param value 要添加的元素
   */
  public void zAdd(String key, double score, Object value) {
    redisTemplate.opsForZSet().add(key, value, score);
  }

  /**
   * 获取有序集合中指定分数区间内的元素
   *
   * @param key 有序集合的键
   * @param min 最小分数，包含，可使用"-inf"表示负无穷
   * @param max 最大分数，包含，可使用"+inf"表示正无穷
   * @return 元素集合
   */
  public Set<Object> zRangeByScore(String key, double min, double max) {
    return redisTemplate.opsForZSet().rangeByScore(key, min, max);
  }

  /**
   * 获取有序集合中指定排名区间内的元素
   *
   * @param key 有序集合的键
   * @param start 起始排名（包含），0表示第一个元素
   * @param end 结束排名（包含）
   * @return 元素集合
   */
  public Set<Object> zRange(int start, int end, String key) {
    return redisTemplate.opsForZSet().range(key, start, end);
  }

  /**
   * 获取有序集合中元素的分数
   *
   * @param key 有序集合的键
   * @param value 要获取分数的元素
   * @return 元素的分数，如果元素不存在则返回null
   */
  public Double zScore(String key, Object value) {
    return redisTemplate.opsForZSet().score(key, value);
  }

  /**
   * 删除有序集合中的元素
   *
   * @param key 有序集合的键
   * @param values 要删除的元素集合
   * @return 删除成功的元素个数
   */
  public Long zRem(String key, Object... values) {
    return redisTemplate.opsForZSet().remove(key, values);
  }

  // 哈希类型操作

  /**
   * 向哈希表中添加字段及对应的值
   *
   * @param key 哈希表的键
   * @param hashKey 字段名
   * @param value 字段对应的值
   */
  public void hPut(String key, String hashKey, Object value) {
    redisTemplate.opsForHash().put(key, hashKey, value);
  }

  /**
   * 向哈希表中批量添加字段及对应的值
   *
   * @param key 哈希表的键
   * @param map 字段及对应值的映射表
   */
  public void hPutAll(String key, Map<String, Object> map) {
    redisTemplate.opsForHash().putAll(key, map);
  }

  /**
   * 获取哈希表中指定字段的值
   *
   * @param key 哈希表的键
   * @param hashKey 字段名
   * @return 字段对应的值，如果字段不存在则返回null
   */
  public Object hGet(String key, String hashKey) {
    return redisTemplate.opsForHash().get(key, hashKey);
  }

  /**
   * 获取哈希表中所有的字段及对应的值
   *
   * @param key 哈希表的键
   * @return 字段及对应值的映射表
   */
  public Map<Object, Object> hGetAll(String key) {
    return redisTemplate.opsForHash().entries(key);
  }

  /**
   * 删除哈希表中的指定字段
   *
   * @param key 哈希表的键
   * @param hashKeys 要删除的字段集合
   * @return 删除成功的字段个数
   */
  public Long hDel(String key, Object... hashKeys) {
    return redisTemplate.opsForHash().delete(key, hashKeys);
  }

  /**
   * 设置指定key的过期时间（以秒为单位）
   *
   * @param key 要设置过期时间的键
   * @param expireTime 过期时间，以秒为单位
   * @return 如果操作成功返回true，否则返回false
   */
  public boolean setExpireForKey(String key, long expireTime) {
    return Boolean.TRUE.equals(redisTemplate.expire(key, expireTime, TimeUnit.SECONDS));
  }

  /**
   * 设置指定key的过期时间（以毫秒为单位）
   *
   * @param key 要设置过期时间的键
   * @param expireTimeMs 过期时间，以毫秒为单位
   * @return 如果操作成功返回true，否则返回false
   */
  public boolean setExpireForKeyInMillis(String key, long expireTimeMs) {
    return Boolean.TRUE.equals(redisTemplate.expire(key, expireTimeMs, TimeUnit.MILLISECONDS));
  }

  /**
   * 设置指定key的过期时间到给定的日期
   *
   * @param key 要设置过期时间的键
   * @param date 过期的具体日期
   * @return 如果操作成功返回true，否则返回false
   */
  public boolean setExpireAtDate(String key, java.util.Date date) {
    return Boolean.TRUE.equals(redisTemplate.expireAt(key, date));
  }

  /**
   * 尝试获取分布式锁
   *
   * @param lockKey 锁的键
   * @param requestId 请求ID 用于标识请求
   * @param expireTime 过期时间，单位为秒
   * @return 是否获取到锁
   */
  public boolean tryLock(String lockKey, String requestId, long expireTime) {
    Boolean result =
        redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
    return result != null && result;
  }

  /**
   * 释放锁
   *
   * @param lockKey 锁的键
   * @param requestId 请求ID,防止其它请求释放锁
   */
  public void unlock(String lockKey, String requestId) {
    String currentValue = (String) redisTemplate.opsForValue().get(lockKey);
    if (StringUtils.hasText(currentValue) && currentValue.equals(requestId)) {
      redisTemplate.delete(lockKey);
    }
  }

  /**
   * 使用分布式锁执行给定的回调函数，如果获取锁成功，则执行回调函数并返回结果， 执行完后自动释放锁；如果获取锁失败，则抛出异常或返回默认值（根据回调函数类型）。 获取不到锁直接返回null
   *
   * @param lockKey 锁的键
   * @param expireTime 锁的过期时间（秒）
   * @param callback 要执行的回调函数，支持泛型返回值
   * @param <T> 返回值的类型
   * @return 回调函数执行的结果，如果获取锁失败且回调函数返回类型为引用类型则返回null， 如果返回类型为基本类型则返回对应类型的默认值（如int返回0等）
   */
  public <T> T executeWithLockNoBlock(String lockKey, long expireTime, Supplier<T> callback) {

    String requestId = UUID.randomUUID().toString();

    boolean locked = tryLock(lockKey, requestId, expireTime);
    try {
      if (locked) {
        return callback.get();
      } else {
        return null;
      }
    } finally {
      if (locked) {
        unlock(lockKey, requestId);
      }
    }
  }

  /**
   * 使用分布式锁执行给定的回调函数，如果获取锁成功，则执行回调函数并返回结果， 执行完后自动释放锁；如果获取锁失败，则抛出异常或返回默认值（根据回调函数类型）。 获取不到锁自旋等待
   *
   * @param lockKey 锁的键
   * @param expireTime 锁的过期时间（秒）
   * @param count 自旋次数
   * @param callback 要执行的回调函数，支持泛型返回值
   * @param <T> 返回值的类型
   * @return 回调函数执行的结果，如果获取锁失败且回调函数返回类型为引用类型则返回null， 如果返回类型为基本类型则返回对应类型的默认值（如int返回0等）
   */
  public <T> T executeWithLockBlock(
      String lockKey, long expireTime, int count, Supplier<T> callback) {
    String requestId = UUID.randomUUID().toString();

    if (count <= 0) {
      count = 10;
    }

    try {
      while (!tryLock(lockKey, requestId, expireTime) && count > 0) {
        logger.info("lock not obtained, spin waiting 500 millis...");
        TimeUnit.MILLISECONDS.sleep(500);
        count = count - 1;

        if (count == 0) {
          throw new RuntimeException("lock not obtained after spin waiting");
        }
      }
      return callback.get();
    } catch (Exception ex) {
      logger.error("executeWithLockBlock error:{}", ex.getMessage(), ex);
      throw new RuntimeException(ex);
    } finally {
      unlock(lockKey, requestId);
    }
  }

  /**
   * 检查某个key是否存在
   *
   * @param key 要检查的key
   * @return 如果存在返回true，否则返回false
   */
  public boolean exists(String key) {
    return redisTemplate.hasKey(key);
  }

  /**
   * 自增1
   *
   * @param key 要自增的key
   * @return 自增后的值
   */
  public Long incr(String key) {
    return redisTemplate.opsForValue().increment(key);
  }


  /**
   *  设置key的value，如果key不存在则设置成功，如果key存在则设置失败
   * @param key key
   * @param value  value
   * @param timeout 超时时间
   * @param unit 时间单位
   * @return 设置成功返回true，否则返回false
   */
  public Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
    return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
  }
}
